﻿using ClusteringProblem.Maths.Metrics;
using ClusteringProblem.Model;
using MachineLearning.ClusteringProblem.NumberGenerator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClusteringProblem.Algorithms.TraditionalMethod
{
	public class KMeans : IClusteringAlgorithm
	{
		#region Pola

		// liczba iteracji
		private int iterationCount;

        // liczba grup (parametr k)
		private int groupsCount;

        // miara odległości - metryka
		private IDistance distance;

        // generator liczb
		private MersenneTwister generator;

        // dane do grupowania
		private List<IAntType> data;
		
        // wynikowy podział
		private List<Tuple<int, List<IAntType>>> result;        

		#endregion

		/// <summary>
		/// Konstruktor KMeans
		/// </summary>
		/// <param name="n">liczba iteracji</param>
		/// <param name="k">liczba grup</param>
		/// <param name="distance">wybrana metryka odległości</param>
		public KMeans(int n, int k, IDistance distance)
		{
			this.iterationCount = n;
			this.groupsCount = k;
			this.distance = distance;
		}

		#region Właściwości

		public List<Tuple<int, List<IAntType>>> Result
		{
			get
			{
				return result;
			}
			set
			{
				result = value;
			}
		} 

		#endregion


		/// <summary>
		/// Uruchamia proces grupowania algorytmem KMeans
		/// </summary>
		/// <param name="data">dane do grupowania</param>
		public void RunAlgorithm(List<IAntType> data)
		{
			this.data = data;
            foreach (var item in this.data)
            {
                item.ResultGroupID = 0.ToString();
            }

			result = new List<Tuple<int, List<IAntType>>>();            

			Initialize();
			int counter = 0;

			while (counter < iterationCount)
			{
				UpdateClustering();
				UpdateMeans();
				counter++;
			}

			Result = result;
		}

		/// <summary>
		/// Aktualizuje średnie
		/// </summary>
		private void UpdateMeans()
		{              
			for (int i = 0; i < result.Count; i++)
			{
				for (int j = 0; j < result[i].Item2[0].Vector.Length; j++)
				{
					result[i].Item2[0].Vector[j] = 0.0;
				}			 
			}               

			// aktualizacja średnich
			for (int i = 0; i < result.Count; i++)
			{
				for (int j = 0; j < result[i].Item2.Count; j++)
				{
					for (int k = 0; k < result[i].Item2[j].Vector.Length; k++)
					{
						result[i].Item2[0].Vector[k] += result[i].Item2[j].Vector[k];
					}
				}
			}

			for (int i = 0; i < result.Count; i++)
			{
				for (int k = 0; k < result[i].Item2[0].Vector.Length; k++)
				{
					result[i].Item2[0].Vector[k] /= result[i].Item2.Count;
				}
			}
			
		}

		/// <summary>
		/// Aktualizacja grupowania
		/// </summary>
		private void UpdateClustering()
		{            
			double[] distance = new double[groupsCount];

			int id;
			for (int i = 0; i < result.Count; i++)
			{
				for (id = 0; id < result[i].Item2.Count; id++)
				{
					for (int j = 0; j < groupsCount; j++)
					{
						distance[i] = Distance(result[i].Item2[id], result[j].Item2[0]);
					}
				}
				
				// pobranie id najbliższej grupy
				int minId = MinimumIndex(distance);
				if (minId != result.IndexOf(result[i]))
				{
					result[minId].Item2.Add(result[i].Item2[id - 1]);
					result[i].Item2.RemoveAt(id- 1);
				}
			}            
		}

		private int MinimumIndex(double[] distance)
		{
			return Array.IndexOf(distance, distance.Min());            
		}

        /// <summary>
        /// Oblicza odległość między obiektami
        /// </summary>
        /// <param name="i">i-ty obiekt</param>
        /// <param name="j">j-ty obiekt</param>
        /// <returns></returns>
		private double Distance(IAntType i, IAntType j)
		{
			return distance.ComputeDistance(i, j);
		}

        /// <summary>
        /// Inicjalizacja średnich, przyporządkowanie obiektów do grup
        /// </summary>
		private void Initialize()
		{
			generator = new MersenneTwister();            
			
			// wylosowanie means dla każdej grupy
			for (int i = 0; i < groupsCount; i++)
			{
				int index = generator.Next(data.Count - 1);
				Tuple<int, List<IAntType>> singleItem = new Tuple<int, List<IAntType>>(i + 1, new List<IAntType>());
                data[index].ResultGroupID = (i + 1).ToString();
				singleItem.Item2.Add(data[index]);
				data.RemoveAt(index);                
				result.Add(new Tuple<int, List<IAntType>>(i + 1, singleItem.Item2));
			}

			// przyporządkowanie pozostałych obiektów do grup
			for (int i = 0; i < data.Count; i++)
			{
				int index = generator.Next(0, groupsCount - 1);				                
                data[i].ResultGroupID = (index + 1).ToString();				
				result[index].Item2.Add(data[i]);
			}
        }
			   
	}
}
